package com.beiding;

import sun.misc.Resource;
import sun.misc.URLClassPath;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;


/*

      设类加载器为T,父类加载器为P

      if: T 可以加载到
        => if: P 可以加载到
            => 返回P加载的结果
           else
            => 返回T加载的结果

 */
public class RemoveAbleUrlClassLoader extends URLClassLoader {

    private ClassLoader parent;

    public RemoveAbleUrlClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, null);
        this.parent = parent;
    }

    /*
        如果自己能够加载,判断父类加载器是否能够加载,如果父类加载器可以加载到就采用父类加载器加载的结果

        该类加载器只是对资源进行隐藏,但是并不会实际加载任何类

     */

    private URLClassPath ucp = (URLClassPath) getField(this, "ucp", URLClassLoader.class);

    private boolean exist(String name) {
        String path = name.replace('.', '/').concat(".class");
        return ucp.getResource(path, false) != null;
    }


    /*
        如果父类加载器中存在就使用父类加载器加载的结果,如果父类加载器加载不到就自己加载

        相对于常规的类加载器,该类加载器可以屏蔽掉一些url资源

     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        if (exist(name)) {
            try {
                Class<?> loadClass = parent.loadClass(name);
                if (loadClass != null) {
                    return loadClass;
                }
            } catch (ClassNotFoundException ignore) {
            }
            return super.findClass(name);
        } else {
            throw new ClassNotFoundException(name);
        }
    }

    public Set<URL> remove(String ant) {
        ant = ant.replace(".", "\\.");
        final String start = "[/]?([^/]+/)*";
        final String startHolder = "##startHolder##";

        final String end = "(/[^/]+)*[/]?";
        final String endHolder = "##endHolder##";
        final String inner = "/([^/]+/)*";
        final String innerHolder = "##innerHolder##";

        if (ant.startsWith("**/")) {
            ant = startHolder + ant.substring(3);
        }

        if (ant.endsWith("/**")) {
            ant = ant.substring(0, ant.lastIndexOf("/**")) + endHolder;
        }

        ant = ant.replace("/**/", innerHolder);


        if (ant.contains("**")) {
            throw new RuntimeException("错误的连续通配符");
        }

        ant = ant.replace("*", "[^.]+");
        ant = ant.replace(startHolder, start);
        ant = ant.replace(endHolder, end);
        ant = ant.replace(innerHolder, inner);
        return removeUrls(ant);
    }

    private Set<URL> removeUrls(String rule) {
        Set<URL> r = new HashSet<>();

        //找到所有的内部资源

        boolean closed = (boolean) getField(this.ucp, "closed", URLClassPath.class);
        final Stack<URL> urls = (Stack<URL>) getField(this.ucp, "urls", URLClassPath.class);
        ArrayList<URL> path = (ArrayList<URL>) getField(this.ucp, "path", URLClassPath.class);
        URL[] lookupCacheURLs = (URL[]) getField(this.ucp, "lookupCacheURLs", URLClassPath.class);
        if (closed)
            return r;
        synchronized (urls) {
            Iterator<URL> iterator = urls.iterator();
            while (iterator.hasNext()) {
                URL next = iterator.next();
                if (next.getPath().matches(rule)) {
                    iterator.remove();
                    path.remove(next);
                    r.add(next);
                }
            }

            if (lookupCacheURLs != null) {
                try {
                    Method disableAllLookupCaches = URLClassPath.class.getDeclaredMethod("disableAllLookupCaches");
                    disableAllLookupCaches.setAccessible(true);
                    disableAllLookupCaches.invoke(null);
                } catch (Exception ignore) {
                }
            }
        }
        return r;
    }


    private static Object getField(Object object, String name, Class type) {

        try {
            Field declaredField = type.getDeclaredField(name);
            declaredField.setAccessible(true);
            return declaredField.get(object);
        } catch (IllegalAccessException | NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

}
